package com.ssm.security.JWT;

import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 处理login的程序，包括用户名密码验证，验证成功与失败的处理
 *处理验证令牌、令牌校验通过与否的处理
 */
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {

    static final String HEADER_USER_NAME = "username";

    protected final Log logger = LogFactory.getLog(this.getClass());

    /**
     * TokenAuthenticationService是从security.xml的配置文件注入
     * 也可以直接调用个，但是为了能在TokenAuthenticationService中使用
     * UserDetailsService，只能通过注入的方式进行使用
     */
    private TokenAuthenticationService tokenAuthenticationService;

    public TokenAuthenticationService getTokenAuthenticationService() {
        return tokenAuthenticationService;
    }

    public void setTokenAuthenticationService(TokenAuthenticationService tokenAuthenticationService) {
        this.tokenAuthenticationService = tokenAuthenticationService;
    }

    /**
     * 构造方法的两个参数是从配置文件注入的
     * @param url   定义拦截登录的URL
     * @param authManager   定义用户名密码的验证管理器
     */
    public JWTLoginFilter(String url, AuthenticationManager authManager) {
        super(new AntPathRequestMatcher(url));
        setAuthenticationManager(authManager);
    }

    /**
     * 根据请求生成账户主体
     * 再根据主体中的用户名密码生成一个验证令牌，这个验证令牌在给
     * AbstractAuthenticationProcessingFilter进行用户名密码校验
     * @param req
     * @param res
     * @return
     * @throws AuthenticationException
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException {
        if( !req.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + req.getMethod());
        }else {
            AccountCredentials creds = null;
            ServletInputStream a = req.getInputStream();
            try {
                creds = new ObjectMapper().readValue(req.getInputStream(), AccountCredentials.class);
            } catch (JsonMappingException e) {
                throw new AuthenticationServiceException("Post Parameter not supported" );
            }

            // JSON反序列化成 AccountCredentials
            if (creds == null) {
                throw new AuthenticationServiceException("Post Parameter not supported" );
            }
            // 返回一个验证令牌
            return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken( creds.getUsername(), creds.getPassword()));
        }
    }

    /**
     * 对父类的successfulAuthentication方法进行重写
     * 上面的校验通过后执行这里的successfulAuthentication
     * 此处的tokenAuthenticationService是由security.xml的配置文件注入的，也可以直接调用对象
     * 但是在tokenAuthenticationService中需要在配置文件中注入UserDetailsService对象，该对象不能直接调用对象，只能通过配置文件进行注入
     * 所以这里的tokenAuthenticationService使用了注入的方式。
     * 该方法主要是为了生成一个token，并在response中加入token信息返回给前端
     * --此处是登陆成功后的执行程序--
     * @param req
     * @param res
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest req,HttpServletResponse res, FilterChain chain,Authentication auth) throws IOException, ServletException {
        tokenAuthenticationService.addAuthentication(req,res, auth);
    }

    /**
     * 与上面功能相似，用户名密码验证失败后执行，并且返回必要信息
     * --此处
     * 1.用户登录时密码验证失败的执行程序--
     *
     * 与com.ssm.security.authorization.MyExceptionTranslationFilter.accessDeniedHandler.handle
     * com.ssm.security.authorization.MyExceptionTranslationFilter.authenticationEntryPoint.commence
     * 这两个错误处理程序要区分
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        try {
            if(!response.isCommitted()){
                logger.info("user "+"'"+request.getHeader(HEADER_USER_NAME)+"'"+" Authentication failed");
                response.setContentType("application/json");
                response.setStatus(HttpServletResponse.SC_OK);
                response.setHeader("Authentication", JSONResult.fillResultString(500, failed.getMessage(), null, null,null));
            }
        } catch (IOException e) {
            throw new IOException(e.getMessage());
        }
    }


}


